package main

import (
	"fmt"
	"os"
	"path"
	"time"

	"github.com/opensourceways/go-gitee/gitee"
	"github.com/opensourceways/repo-file-cache/dbmodels"
	"github.com/opensourceways/repo-file-cache/models"
	"github.com/opensourceways/repo-file-cache/sdk"
	"github.com/opensourceways/robot-gitee-lib/client"
	"github.com/opensourceways/server-common-lib/utils"
	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/util/sets"
)

const (
	token      = "xxxxxxxxx"
	endpoint   = "http://localhost:80/v1/file"
	configFile = "./config.yaml"
)

var (
	giteeCli     client.Client
	fileCacheCli *sdk.SDK
)

type config struct {
	Repos []repoConfig `json:"repos"`
}

// repoConfig
type repoConfig struct {
	// Platform is the code platform.
	Platform string `json:"platform"        required:"true"`

	// FileNames is the list of files to be synchronized.
	FileNames []string `json:"file_names"   required:"true"`

	OrgRepos []orgRepos `json:"org_repos,omitempty"`
}

type orgRepos struct {
	IsPersonal    bool     `json:"is_personal"`
	Org           string   `json:"org" required:"true"`
	Repos         []string `json:"repos,omitempty"`
	ExcludedRepos []string `json:"excluded_repos,omitempty"`
}

type Param struct {
	Platform string
	Org      string
	Repo     string
	Branch   Branch
	Path     string
}

type Branch struct {
	Name string
	SHA  string
}

type Process struct {
	OrgRepos []orgRepos
	Files    []string
	param    Param
}

func init() {
	giteeCli = client.NewClient(func() []byte {
		return []byte(token)
	})

	fileCacheCli = sdk.NewSDK(endpoint, 3)
}

func main() {
	syncJob()
}

func syncJob() {
	today := time.Now().Format("2006-01-02")
	logFileName := fmt.Sprintf("./%s.log", today)
	logfile, _ := os.OpenFile(logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	logrus.SetOutput(logfile)

	cfg, err := loadConfig(configFile)
	if err != nil {
		logrus.Errorln("load config", err.Error())

		return
	}

	for _, c := range cfg.Repos {
		process := Process{
			OrgRepos: c.OrgRepos,
			Files:    c.FileNames,
			param: Param{
				Platform: c.Platform,
			},
		}
		process.handleRepoConfig()
	}
}

func loadConfig(path string) (*config, error) {
	cfg := new(config)
	if err := utils.LoadFromYaml(path, cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}

func (p *Process) handleRepoConfig() {
	for _, c := range p.OrgRepos {
		var repoNames []string
		if c.IsPersonal {
			repoNames = c.Repos
		} else {
			repos, err := giteeCli.GetRepos(c.Org)
			if err != nil {
				logrus.Errorln("get repos of", c.Org, err.Error())
				continue
			}

			if len(repos) == 0 {
				continue
			}

			for i := range repos {
				repoNames = append(repoNames, repos[i].Path)
			}
		}

		filterRepos := c.filterRepos(repoNames)
		p.param.SetOrg(c.Org)
		p.handleRepos(filterRepos)
	}
}

func (p *Process) handleRepos(repos []string) {
	for _, repo := range repos {
		p.param.SetRepo(repo)
		ret, err := giteeCli.GetRepoAllBranch(p.param.Org, p.param.Repo)
		if err != nil {
			logrus.Errorln("get branches of", p.param.String(), err.Error())
			continue
		}
		if len(ret) == 0 {
			continue
		}

		branches := make([]Branch, len(ret))
		for i := range ret {
			branches[i] = Branch{
				Name: ret[i].GetName(),
				SHA:  ret[i].GetCommit().GetSha(),
			}
		}

		p.handleBranches(branches)
	}
}

func (p *Process) handleBranches(branches []Branch) {
	for _, b := range branches {
		p.param.SetBranch(b)
		cache, err := p.getCacheOfBranch()
		if err != nil {
			logrus.Errorln("get cache info of", p.param.String(), err.Error())
			continue
		}

		if len(cache) == 0 {
			logrus.Infoln("branch does not change", p.param.String())
			continue
		}

		trees, err := giteeCli.GetDirectoryTree(p.param.Org, p.param.Repo, p.param.Branch.Name, 1)
		if err != nil {
			logrus.Errorln("get directory tree of", p.param.String(), err.Error())
			continue
		}

		for _, v := range trees.Tree {
			basePath := path.Base(v.Path)
			if !sets.NewString(p.Files...).Has(basePath) {
				continue
			}

			p.param.SetPath(v.Path)
			if p.isFileNotChange(cache[basePath], v) {
				logrus.Infoln("file does not change", p.param.String())
				continue
			}

			p.handleFile()

			time.Sleep(time.Second * 1)
		}
	}
}

func (p *Process) handleFile() {
	content, err := giteeCli.GetPathContent(p.param.Org, p.param.Repo, p.param.Path, p.param.Branch.Name)
	if err != nil {
		logrus.Errorln("get file content of", p.param.String(), err.Error())
		return
	}

	opts := models.FileUpdateOption{
		Branch: models.Branch{
			Platform: p.param.Platform,
			Org:      p.param.Org,
			Repo:     p.param.Repo,
			Branch:   p.param.Branch.Name,
		},
	}
	opts.BranchSHA = p.param.Branch.SHA
	opts.Files = []models.File{
		{
			Path:    models.FilePath(p.param.Path),
			SHA:     content.Sha,
			Content: content.Content,
		},
	}

	if err = fileCacheCli.SaveFiles(opts); err != nil {
		logrus.Errorln("save files of", p.param.String(), err.Error())

		return
	}

	logrus.Infoln("sync success of", p.param.String())
}

func (p *Process) isFileNotChange(cache models.FilesInfo, file gitee.TreeBasic) bool {
	for _, v := range cache.Files {
		if v.Path.FullPath() == file.Path && v.SHA == file.Sha {
			return true
		}
	}

	return false
}

func (p *Process) getCacheOfBranch() (map[string]models.FilesInfo, error) {
	ret := make(map[string]models.FilesInfo)

	for _, file := range p.Files {
		req := dbmodels.Branch{
			Platform: p.param.Platform,
			Org:      p.param.Org,
			Repo:     p.param.Repo,
			Branch:   p.param.Branch.Name,
		}
		info, err := fileCacheCli.GetFiles(req, file, false)
		if err != nil {
			return nil, err
		}

		if info.BranchSHA == p.param.Branch.SHA {
			continue
		}

		if info.BranchSHA != "" {
			p.updateBranchSha(info)
		}

		ret[file] = info
	}

	return ret, nil
}

func (p *Process) updateBranchSha(info models.FilesInfo) {
	opts := models.FileUpdateOption{
		Branch: models.Branch{
			Platform: p.param.Platform,
			Org:      p.param.Org,
			Repo:     p.param.Repo,
			Branch:   p.param.Branch.Name,
		},
		FilesInfo: models.FilesInfo{
			BranchSHA: p.param.Branch.SHA,
			Files:     info.Files,
		},
	}

	if err := fileCacheCli.SaveFiles(opts); err != nil {
		logrus.Errorln("update branch sha of", p.param.String(), err.Error())
	}
}

func (o *orgRepos) filterRepos(repos []string) []string {
	if len(o.Repos) > 0 {
		return sets.NewString(repos...).Intersection(
			sets.NewString(o.Repos...),
		).UnsortedList()
	}

	if len(o.ExcludedRepos) > 0 {
		return sets.NewString(repos...).Difference(
			sets.NewString(o.ExcludedRepos...),
		).UnsortedList()
	}

	return repos
}

func (p *Param) String() string {
	return fmt.Sprintf("[%s %s %s %s]", p.Org, p.Repo, p.Branch.Name, p.Path)
}

func (p *Param) SetOrg(org string) {
	p.Org = org
	p.Repo = ""
	p.Branch = Branch{}
	p.Path = ""
}

func (p *Param) SetRepo(repo string) {
	p.Repo = repo
	p.Branch = Branch{}
	p.Path = ""
}

func (p *Param) SetBranch(b Branch) {
	p.Branch = b
	p.Path = ""
}

func (p *Param) SetPath(path string) {
	p.Path = path
}
